package com.artemis;

import com.artemis.utils.*;

/**
 * A {@link ComponentManager} that put {@link Component}s back to their {@link Pool}
 * when they are removed from an {@link Entity}.
 * This will only done if a pool is set for a certain {@link ComponentType}.
 * To obtain a component from a pool, a pool for that type of component must set before.
 * 
 * @author kryo
 *
 */
public class PooledComponentManager extends ComponentManager {
	private Bag<Pool<Component>> pools;
	
	public PooledComponentManager() {
		super();
		pools = new Bag<Pool<Component>>();
	}
	
	@Override
	protected void freeComponent(int typeId, Component component) {
		Pool<Component> pool = pools.get(typeId);
		if(pool != null) pool.free(component);
	}
	
	/**
	 * Obtain a {@link Component} of a certain type from the Pool.
	 * 
	 * @param type the type of the component.
	 * 
	 * @return a component of that type or null if no Pool for that type is set.
	 */
	public <T extends Component> T obtain(Class<T> type) {
		return obtain(ComponentType.getTypeFor(type), type);
	}
	
	/**
	 * Obtain a {@link Component} of a certain type from the Pool.
	 * 
	 * @param type the type of the component.
	 * @param clazz the type of the component (only used for type-casting).
	 * 
	 * @return a component of that type or null if no Pool for that type is set.
	 */
	@SuppressWarnings("unchecked")
	public <T extends Component> T obtain(ComponentType type, Class<T> clazz) {
		Pool<Component> pool = getPool(type);
		if(pool != null) return (T) pool.obtain();
		return null;
	}
	
	/**
	 * Obtain a {@link Component} of a certain type from the Pool.
	 * 
	 * @param type the type of the component.
	 * 
	 * @return a component of that type or null if no Pool for that type is set.
	 */
	public Component obtain(ComponentType type) {
		Pool<Component> pool = getPool(type);
		if(pool != null) return pool.obtain();
		return null;
	}
	
	/**
	 * Checks whether a pool for a certain component type exists.
	 * @param <T>
	 * 
	 * @param type the type of the component.
	 * 
	 * @return whether it has a pool for that type.
	 */
	public <T extends Component> boolean hasPool(Class<T> type) {
		ComponentType t = ComponentType.getTypeFor(type);
		return pools.isIndexWithinBounds(t.getIndex()) && pools.get(t.getIndex()) != null;
	}
	
	/**
	 * Checks whether a pool for a certain component type exists.
	 * 
	 * @param type the type of the component.
	 * 
	 * @return whether it has a pool for that type.
	 */
	public boolean hasPool(ComponentType type) {
		return pools.isIndexWithinBounds(type.getIndex()) && pools.get(type.getIndex()) != null;
	}
	
	/**
	 * Obtain a pool for a certain component type.
	 * 
	 * @param type the type of the component.
	 * 
	 * @return a pool or null if no Pool for that type is set.
	 */
	@SuppressWarnings("unchecked")
	public <T extends Component> Pool<T> getPool(Class<T> type) {
		ComponentType t = ComponentType.getTypeFor(type);
		if(!pools.isIndexWithinBounds(t.getIndex())) return null;
		return (Pool<T>) pools.get(t.getIndex());
	}
	
	/**
	 * Obtain a pool for a certain component type.
	 * 
	 * @param type the type of the component.
	 * 
	 * @return a pool or null if no Pool for that type is set.
	 */
	public Pool<Component> getPool(ComponentType type) {
		if(!pools.isIndexWithinBounds(type.getIndex())) return null;
		return pools.get(type.getIndex());
	}
	
	/**
	 * Set a pool for a certain component type.
	 * 
	 * @param pool the pool for a certain type.
	 * @param type the type of the component.
	 */
	@SuppressWarnings("unchecked")
	public <T extends Component> void setPool(Pool<T> pool, Class<T> type) {
		ComponentType t = ComponentType.getTypeFor(type);
		pools.ensureCapacity(t.getIndex());
		pools.set(t.getIndex(), (Pool<Component>) pool);
	}
	
	/**
	 * Set a pool for a certain component type.
	 * 
	 * @param pool the pool for a certain type.
	 * @param type the type of the component.
	 */
	@SuppressWarnings("unchecked")
	public void setPool(Pool<? extends Component> pool, ComponentType type) {
		pools.ensureCapacity(type.getIndex());
		pools.set(type.getIndex(), (Pool<Component>) pool);
	}
	
	/**
	 * Removes a pool for a certain component type.
	 * 
	 * @param type the type of the component.
	 */
	public <T extends Component> void removePool(Class<T> type) {
		ComponentType t = ComponentType.getTypeFor(type);
		if(!pools.isIndexWithinBounds(t.getIndex())) return;
		pools.set(t.getIndex(), null);
	}
	
	/**
	 * Removes a pool for a certain component type.
	 * 
	 * @param type the type of the component.
	 */
	public void removePool(ComponentType type) {
		if(!pools.isIndexWithinBounds(type.getIndex())) return;
		pools.set(type.getIndex(), null);
	}
}
